/* Copyright (C) 2007-2010 STS Soft

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; version 2 of the License.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */

using System;
using STSdb.Files;
using STSdb.Volumes;
using System.IO;

namespace STSdb.Data
{
    public class StorageEngine : IDisposable
    {
        private bool disposed = false;

        //ID -> Offset
        internal XTable<ulong, ulong> JournalScheme;

        public FileSystem FileSystem { get; private set; }
        /// <summary>
        /// Table scheme.
        /// </summary>
        public readonly Scheme Scheme;

        public StorageEngine(FileSystem fileSystem)
        {
            if (fileSystem == null)
                throw new ArgumentNullException("fileSystem");

            FileSystem = fileSystem;

            RawFile schemeFile;
            RawFile journalFile;
            if (fileSystem.FileCount == 0)
            {
                schemeFile = FileSystem.CreateFile(); //0
                journalFile = FileSystem.CreateFile(); //1

                FileSystem.Flush();
                journalFile.Flush();

                for (int i = (int)FileSystem.FileCount; i < 4; i++) //up to 4 reserved files
                    FileSystem.CreateFile();
            }
            else
            {
                schemeFile = FileSystem.OpenFile(0);
                journalFile = FileSystem.OpenFile(1);
            }

            //----Journal----- Always first to open
            JournalScheme = new XTable<ulong, ulong>(journalFile, 0);
            foreach (var row1 in JournalScheme)
            {
                XTable<ulong, JournalItem> journal = new XTable<ulong, JournalItem>(journalFile, row1.Record);
                foreach (var row in journal)
                {
                    RawFile file = FileSystem.OpenFile(row.Record.Handle);
                    file.Write(row.Record.Offset, row.Record.Data, 0, row.Record.Data.Length);
                    file.Close();
                }
            }
            JournalScheme.Clear();
            JournalScheme.Commit();

            Scheme = new Scheme(schemeFile, 0, this);
        }

        #region IDisposable Members

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                if (disposing)
                {
                    FileSystem.Dispose();
                    FileSystem = null;
                }
            }
            disposed = true;
        }

        ~StorageEngine()
        {
            Dispose(false);
        }

        protected void CheckDisposal()
        {
            if (this.disposed)
                throw new ObjectDisposedException("Repository is disposed.");
        }

        #endregion

        public void CopyData(StorageEngine dstEngine)
        {
            Scheme srcScheme = this.Scheme; //new Scheme(this.FileSystem.OpenFile(0), 0, this);
            Scheme dstScheme = dstEngine.Scheme;

            srcScheme.CopyTables(dstScheme);

            //TODO: copy also all user files from this.FileSystem to dstEngine.FileSystem
            //TODO: implement CopyUserFiles(engine1, engine2) method.
        }

        public ulong GetTotalTableSize()
        {
            ulong dataSize = 0;
            ulong systemSize = 0;

            foreach (var schemeRow in Scheme)
            {
                Type tableType = schemeRow.Record.Type;
                Type keyType = tableType.GetGenericArguments()[0];
                Type recordType = tableType.GetGenericArguments()[1];

                var table = Scheme.CreateOrOpenXTable(schemeRow.Key, keyType, recordType);

                dataSize += table.DataSize;
                systemSize += table.SystemSize;
            }

            return dataSize + systemSize;
        }

        public static ulong GetTotalTableSize(string fileName)
        {
            using (StorageEngine engine = StorageEngine.FromFile(fileName))
                return engine.GetTotalTableSize();
        }

        public static void CopyData(string srcFileName, string dstFileName)
        {
            using (StorageEngine srcEngine = StorageEngine.FromFile(srcFileName))
            {
                using (StorageEngine dstEngine = StorageEngine.FromFile(dstFileName))
                {
                    srcEngine.CopyData(dstEngine);
                    dstEngine.Scheme.Commit();
                }
            }
        }

        public static void Compact(string fileName, string tmpFileName)
        {
            if (tmpFileName == null)
                tmpFileName = fileName + ".temp";
            
            CopyData(fileName, tmpFileName);
            File.Delete(fileName);
            File.Move(tmpFileName, fileName);
        }

        public static void Compact(string fileName, double onWastedSpaceInPercent, string tmpFileName)
        {
            FileInfo info = new FileInfo(fileName);
            ulong tableSize = GetTotalTableSize(fileName);
            double percent = 100.0 - 100.0 * tableSize / info.Length;

            if (percent >= onWastedSpaceInPercent)
                Compact(fileName, tmpFileName);
        }

        public static StorageEngine FromMemory(ulong quota)
        {
            return new StorageEngine(FileSystem.FromMemory(quota));
        }

        public static StorageEngine FromFile(string fileName)
        {
            return new StorageEngine(FileSystem.FromFile(fileName));
        }

        public static StorageEngine FromChain(VolumeChain volumeChain)
        {
            return new StorageEngine(FileSystem.FromChain(volumeChain));
        }
    }
}
